<script setup lang="tsx">
/**
 * 比对组件，用于展示两个文件的差异，支持JSON格式的对比。
 * @Date 2025年9月18日21:41:36
 * @Author Geng Kaibo
 */
import type { Diff2HtmlUIConfig } from 'diff2html/lib/ui/js/diff2html-ui'
import { createTwoFilesPatch } from 'diff'
import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui'
import { deepMerge } from 'fetm-utils'
import { onMounted, ref, shallowRef } from 'vue'

defineOptions({
  name: 'DiffComparator',
})

const props = defineProps<{
  sources: Array<{
    name: string
    content: string
  }>
  dark?: boolean
  config?: Diff2HtmlUIConfig
}>()

const diffElRef = ref<HTMLDivElement | null>(null)
const diff2htmlUi = shallowRef<Diff2HtmlUI | null>(null)

watch(
  [() => props.sources, () => props.config, () => props.dark],
  () => {
    internalRender(props.sources, props.config, props.dark)
  },
  {
    deep: true,
  },
)

onMounted(() => {
  if (props.sources) {
    internalRender(props.sources, props.config, props.dark)
  }
})

/**
 * 内部渲染函数
 * @param sources 源代码数组，每个元素包含一个对象，对象包含name和content属性
 * @param config Diff2HtmlUI的配置项，默认为空对象
 */
function internalRender(
  sources: Array<{
    name: string
    content: string
  }>,
  config: Diff2HtmlUIConfig = {},
  dark: boolean = false,
) {
  if (sources.length > 1) {
    let content0 = sources[0].content
    let content1 = sources[1].content
    try {
      content0 = JSON.parse(content0)
      content1 = JSON.parse(content1)
      // 如果是json则进行预处理，将对象序列化为 格式化的 JSON 字符串（带缩进、换行）
      // 按字母序排序对象属性，确保 { a: 1, b: 2 } 和 { b: 2, a: 1 } 被视为相同
      // 序列化后（属性已排序）：
      content0 = deepSort(content0)
      content1 = deepSort(content1)
      content0 = JSON.stringify(content0, null, 2)
      content1 = JSON.stringify(content1, null, 2)
    }
    catch (error) {}
    const diffString = createTwoFilesPatch(
      sources[0].name,
      sources[1].name,
      content0,
      content1,
      undefined,
      undefined,
      {
        context: Number.MAX_SAFE_INTEGER,
      },
    ) as string
    const configuration: Diff2HtmlUIConfig = deepMerge(
      {
        // 是否绘制文件列表
        drawFileList: false,
        // 是否允许折叠文件列表
        fileListToggle: false,
        // 是否初始展开文件列表
        fileListStartVisible: true,
        // 是否允许折叠文件内容
        fileContentToggle: false,
        // 匹配颗粒度，行匹配
        matching: 'lines',
        // 输出格式，并排比较
        outputFormat: 'side-by-side',
        // 同步滚动
        synchronisedScroll: true,
        // 是否高亮代码块
        highlight: true,
        // 当差异内容为空时是否隐藏输出，设为 true 可隐藏无变化的差异结果，适合仅关注变更的场景。
        renderNothingWhenEmpty: false,
        // @ts-ignore
        colorScheme: dark ? 'dark' : 'light',
      },
      config ?? {},
    )
    diff2htmlUi.value = new Diff2HtmlUI(
      diffElRef.value!,
      diffString,
      configuration,
    )
    diff2htmlUi.value.draw()
    diff2htmlUi.value.highlightCode()
  }
  else {
    console.warn('DiffComparator: sources length must be greater than 1')
  }
}

/**
 * 对对象进行深度排序
 * @param obj 需要排序的对象
 * @returns 排序后的对象
 */
function deepSort(obj: any) {
  if (typeof obj !== 'object' || obj === null) {
    return obj // 非对象直接返回
  }

  if (Array.isArray(obj)) {
    return obj.map(deepSort) // 递归处理数组元素
  }
  const sortedObj = {}
  Object.keys(obj)
    .sort() // 排序当前层的属性
    .forEach((key) => {
      sortedObj[key] = deepSort(obj[key]) // 递归处理子对象
    })
  return sortedObj
}

defineExpose({
  diff2htmlUi,
})
</script>

<template>
  <div
    class="fetm-diff-container h-full w-full"
    :class="[props.dark ? 'dark' : '']"
  >
    <div ref="diffElRef" class="fetm-diff-comparator h-full w-full" />
  </div>
</template>

<style lang="less">
@import './github.less';
@import './github-dark.less';
@import 'diff2html/bundles/css/diff2html.min.css';
</style>
